﻿/**
 * Copyright (C) 2012 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using NGuice.Inject.Internal.Util;
using System.Reflection;
using AopAlliance.Intercept;

namespace NGuice.Inject.Internal
{
    /// <summary>
    /// 由于C#泛型没有通配符，使用标记接口解决此问题！
    /// </summary>
    internal interface ConstructorBindingImpl { }

    internal sealed class ConstructorBindingImpl<T> : BindingImpl<T>, IConstructorBinding<T>, ConstructorBindingImpl
    {
        private readonly Factory<T> factory;
        private readonly InjectionPoint constructorInjectionPoint;

        private ConstructorBindingImpl(InjectorImpl injector, Key<T> key, object source, InternalFactory<T> scopedFactory,
                                       Scoping scoping, Factory<T> factory, InjectionPoint constructorInjectionPoint)
            : base(injector, key, source, scopedFactory, scoping)
        {
            this.factory = factory;
            this.constructorInjectionPoint = constructorInjectionPoint;
        }

        public ConstructorBindingImpl(Key<T> key, object source, Scoping scoping, InjectionPoint constructorInjectionPoint, ISet<InjectionPoint> injectionPoints)
            : base(source, key, scoping)
        {
            this.factory = new Factory<T>(false, key);
            ConstructionProxy<T> constructionProxy = new DefaultConstructionProxyFactory<T>(constructorInjectionPoint).Create();
            this.constructorInjectionPoint = constructorInjectionPoint;
            factory.constructorInjector = new ConstructorInjector<T>(injectionPoints, constructionProxy, null, null);
        }

        /// <summary>
        /// </summary>
        /// <param name="injector"></param>
        /// <param name="key"></param>
        /// <param name="constructorInjector">使用的构造器</param>
        /// <param name="source"></param>
        /// <param name="scoping"></param>
        /// <param name="errors"></param>
        /// <param name="failIfNotLinked">如果此构造器绑定实现的内部工厂仅仅从连接的binding中获取成功，则为true</param>
        /// <returns></returns>
        /// <exception cref="ErrorsException"></exception>
        internal static ConstructorBindingImpl<T> Create(InjectorImpl injector, Key<T> key, InjectionPoint constructorInjector,
            object source, Scoping scoping, Errors errors, bool failIfNotLinked)
        {
            int numErrors = errors.Size;

            // constructorBinding guarantees type is consistent
            Type rawType = constructorInjector == null ? key.TypeLiteral : constructorInjector.DeclaringType;

            // We can't inject abstract classes.
            if (rawType.IsAbstract || rawType.IsGenericTypeDefinition || rawType.IsInterface)
            {
                errors.MissingImplementation(key);
            }

            // Error: Inner class.
            if (Classes.IsInnerClass(rawType))
            {
                errors.CannotInjectInnerClass(rawType);
            }

            errors.ThrowIfNewErrors(numErrors);

            // Find a constructor annotated @Inject
            if (constructorInjector == null)
            {
                try
                {
                    constructorInjector = InjectionPoint.ForConstructorOf(key.TypeLiteral);
                }
                catch (ConfigurationException e)
                {
                    throw errors.Merge(e.ErrorMessages).ToException();
                }
            }

            // if no scope is specified, look for a scoping annotation on the concrete class
            if (!scoping.IsExplicitlyScoped)
            {
                Type annotatedType = constructorInjector.Member.DeclaringType;
                Type scopeAnnotation = Attributes.FindScopeAttribute(errors, annotatedType);
                if (scopeAnnotation != null)
                {
                    scoping = Scoping.MakeInjectable(Scoping.ForAnnotation(scopeAnnotation), injector, errors.WithSource(rawType));
                }
            }

            errors.ThrowIfNewErrors(numErrors);

            Factory<T> factoryFactory = new Factory<T>(failIfNotLinked, key);
            InternalFactory<T> scopedFactory = Scoping.Scope(key, injector, factoryFactory, source, scoping);

            return new ConstructorBindingImpl<T>(injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector);
        }

        /// <summary>
        /// the result type always agrees with the ConstructorInjector type
        /// </summary>
        /// <param name="injector"></param>
        /// <param name="errors"></param>
        /// <exception cref="ErrorsException "></exception>
        public void Initialize(InjectorImpl injector, Errors errors)
        {
            factory.allowCircularProxy = !injector.options.disableCircularProxies;
            factory.constructorInjector = (ConstructorInjector<T>)injector.constructors.Get(constructorInjectionPoint, errors);
        }

        /// <summary>
        /// 如果此绑定已经被初始化且准备使用，则返回true
        /// </summary>
        /// <returns></returns>
        internal bool IsInitialized()
        {
            return factory.constructorInjector != null;
        }

        /// <summary>
        /// 返回一个注入点，此注入点能够被用于清空构造器存储
        /// </summary>
        /// <returns></returns>
        internal InjectionPoint GetInternalConstructor()
        {
            if (factory.constructorInjector != null)
            {
                return factory.constructorInjector.ConstructionProxy.InjectionPoint;
            }
            else
            {
                return constructorInjectionPoint;
            }
        }

        /** Returns a set of dependencies that can be iterated over to clean up stray JIT bindings. */
        internal ISet<Dependency> GetInternalDependencies()
        {
            ImmutableSet<InjectionPoint>.Builder<InjectionPoint> builder = ImmutableSet<InjectionPoint>.GetBuilder();
            if (factory.constructorInjector == null)
            {
                builder.Add(constructorInjectionPoint);
                // If the below throws, it's OK -- we just ignore those dependencies, because no one
                // could have used them anyway.
                try
                {
                    builder.AddAll(InjectionPoint.ForInstanceMethodsAndFields(constructorInjectionPoint.DeclaringType));
                }
                catch (ConfigurationException ignored) { }
            }
            else
            {
                builder.Add(Constructor).AddAll(InjectableMembers);
            }

            return Dependency<T>.ForInjectionPoints(builder.Build());
        }

        public override V AcceptTargetVisitor<V>(IBindingTargetVisitor<T, V> visitor)
        {
            Preconditions.CheckState(factory.constructorInjector != null, "not initialized");
            return visitor.Visit(this);
        }


        public InjectionPoint Constructor
        {
            get
            {
                Preconditions.CheckState(factory.constructorInjector != null, "Binding is not ready");
                return factory.constructorInjector.ConstructionProxy.InjectionPoint;
            }
        }

        public ISet<InjectionPoint> InjectableMembers
        {
            get
            {
                Preconditions.CheckState(factory.constructorInjector != null, "Binding is not ready");
                return factory.constructorInjector.InjectableMembers;
            }
        }

        /*if[AOP]*/
        public IDictionary<MethodInfo, IList<IMethodInterceptor>> MethodInterceptors
        {
            get
            {
                Preconditions.CheckState(factory.constructorInjector != null, "Binding is not ready");
                return factory.constructorInjector.ConstructionProxy.MethodInterceptors;
            }
        }
        /*end[AOP]*/

        public ISet<Dependency> Dependencies
        {
            get
            {
                return Dependency<T>.ForInjectionPoints(
                    new ImmutableSet<InjectionPoint>.Builder<InjectionPoint>()
                    .Add(Constructor)
                    .AddAll(InjectableMembers)
                    .Build());
            }
        }

        protected internal override BindingImpl<T> WithScoping(Scoping scoping)
        {
            return new ConstructorBindingImpl<T>(null, (Key<T>)Key, Source, factory, scoping, factory, constructorInjectionPoint);
        }

        protected internal override BindingImpl<T> WithKey(Key<T> key)
        {
            return new ConstructorBindingImpl<T>(null, key, Source, factory, Scoping, factory, constructorInjectionPoint);
        }

        public override void ApplyTo(IBinder binder)
        {
            InjectionPoint constructor = Constructor;
            Scoping.ApplyTo(binder.WithSource(Source).Bind((Key<T>)Key).ToConstructor((ConstructorInfo)Constructor.Member));
        }

        public override string ToString()
        {
            return new ToStringBuilder(typeof(IConstructorBinding<T>))
                .Add("key", Key)
                .Add("source", Source)
                .Add("scope", Scoping)
                .ToString();
        }

        public override bool Equals(object obj)
        {
            if (obj is ConstructorBindingImpl<T>)
            {
                ConstructorBindingImpl<T> o = (ConstructorBindingImpl<T>)obj;
                return Key.Equals(o.Key)
                  && Scoping.Equals(o.Scoping)
                  && Objects.Equal(constructorInjectionPoint, o.constructorInjectionPoint);
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return Objects.GetHashCode(Key, Scoping, constructorInjectionPoint);
        }

        private class Factory<T> : InternalFactory<T>
        {
            internal readonly bool failIfNotLinked;
            internal readonly Key key;
            internal bool allowCircularProxy;
            internal ConstructorInjector<T> constructorInjector;

            internal Factory(bool failIfNotLinked, Key key)
            {
                this.failIfNotLinked = failIfNotLinked;
                this.key = key;
            }

            public T Get(Errors errors, InternalContext context, Dependency dependency, bool linked)
            {
                Preconditions.CheckState(constructorInjector != null, "Constructor not ready");

                if (failIfNotLinked && !linked)
                {
                    throw errors.JITDisabled(key).ToException();
                }

                // This may not actually be safe because it could return a super type of T (if that's all the
                // client needs), but it should be OK in practice thanks to the wonders of erasure.
                return (T)constructorInjector.Construct(errors, context, dependency.Key.GetType(), allowCircularProxy);
            }

            object InternalFactory.Get(Errors errors, InternalContext context, Dependency dependency, bool linked)
            {
                return Get(errors, context, dependency, linked);
            }
        }

        IDictionary<MethodInfo, IList<IMethodInterceptor>> IConstructorBinding<T>.MethodInterceptors
        {
            get { return MethodInterceptors; }
        }

        public new Key Key
        {
            get { return base.Key; }
        }

        public new IProvider Provider
        {
            get { return base.Provider; }
        }
    }
}
